package gepetto.reactor;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.TreeSet;

public class Reactor {
	private long minTimeout;
	private Selector selector;
	private TreeSet<TimeHandler> timeouts;
	private TreeSet<IdleHandler> idles;
	private TreeSet<SelectHandler> selecters;
	
	public Reactor () {
		try {
			selector = Selector.open();
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.idles = new TreeSet<IdleHandler> ();
		this.timeouts = new TreeSet<TimeHandler> ();
		this.selecters = new TreeSet<SelectHandler> ();
		updateMinTimeout ();
	}
	
	public void handleEvents () {
		if ((! handleSelecters ()) && (! handleTimeouts ())) {
			handleIdles ();
		}
	}

	public Selector getSelector() {
		return selector;
	}
	
	/***************************************************
	 * 
	 *          IDLES FUNCTIONS
	 * 
	 ***************************************************/

	public void register (IdleHandler handler) {
		idles.add (handler);
		handler.setReactor (this);
	}
	
	public void unregister (IdleHandler handler) {
		idles.remove (handler);
		handler.setReactor (null);
	}
	
	private void handleIdles() {
		LinkedList<IdleHandler> to_remove = new LinkedList<IdleHandler> ();
		for (Iterator<IdleHandler> it = idles.iterator(); it.hasNext(); ) {
			IdleHandler handler = it.next();
			if (! handler.handleEvent()) {
				to_remove.add (handler);
				handler.setReactor (null);
			}
		}
		idles.removeAll (to_remove);
	}
	
	/***************************************************
	 * 
	 *          SELECTERS FUNCTIONS
	 * 
	 ***************************************************/
	
	public void register (SelectHandler handler) {
		selecters.add (handler);
		handler.setReactor (this);
		try {
			handler.getChannel().register(selector, handler.getOperations(), handler);
		} catch (ClosedChannelException e) {
			e.printStackTrace();
		}
	}
	
	public void unregister (SelectHandler handler) {
		selecters.remove (handler);
		handler.setReactor (null);
		handler.getChannel().keyFor(selector).cancel();
	}

	private boolean handleSelecters() {
		LinkedList<SelectHandler> to_remove = new LinkedList<SelectHandler> ();
		try {
			selector.select (minTimeout);
			Set<SelectionKey> keys = selector.selectedKeys();
			for (Iterator<SelectionKey> it = keys.iterator(); it.hasNext(); ) {
				SelectionKey key = it.next();
				SelectHandler handler = (SelectHandler) key.attachment();
				if (! handler.handleEvent()) {
					to_remove.add(handler);
					handler.setReactor (null);
					key.cancel();
				}
			}
			selecters.removeAll (to_remove);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}

	
	/***************************************************
	 * 
	 *          TIMEOUTS FUNCTIONS
	 * 
	 ***************************************************/
	
	public void register (TimeHandler handler) {
		timeouts.add (handler);
		handler.setReactor (this);
		updateMinTimeout ();
	}
	
	public void unregister (TimeHandler handler) {
		timeouts.remove (handler);
		handler.setReactor (null);
		updateMinTimeout ();
	}
	
	private boolean handleTimeouts() {
		boolean none = false;
		LinkedList<TimeHandler> to_remove = new LinkedList<TimeHandler> ();
		for (Iterator<TimeHandler> it = timeouts.iterator(); it.hasNext(); ) {
			TimeHandler time = it.next();
			if (! time.handleEvent()) {
				to_remove.add(time);
				time.setReactor(null);
			}
		}
		timeouts.removeAll(to_remove);
		if (! to_remove.isEmpty()) {
			updateMinTimeout ();
		}
		return none;
	}

	private void updateMinTimeout() {
		minTimeout = Long.MAX_VALUE;
		if (! timeouts.isEmpty()) {
			minTimeout = timeouts.first().getTimeout();
		}
	}
}
